// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2022 Ivan Baidakou

#include <rotor-light.hpp>
#include <chrono>
#include <cstdio>
#include <thread>

using Clock = std::chrono::steady_clock;
using TimeUnit = std::chrono::milliseconds;
namespace rl = rotor_light;

rl::TimePoint get_now() {
  return std::chrono::duration_cast<TimeUnit>(Clock::now().time_since_epoch())
      .count();
}

namespace message {
struct Ping : rl::Message {
  using Message::Message;
  static constexpr auto type_id = __LINE__;
  rl::MessageTypeId get_type_id() const override { return type_id; }
};

struct Pong : rl::Message {
  using Message::Message;
  static constexpr auto type_id = __LINE__;
  rl::MessageTypeId get_type_id() const override { return type_id; }
};
} // namespace message

struct Pinger : rl::Actor<2> {
  using Parent = Actor<2>;

  void initialize() override {
    subscribe(&Pinger::on_pong);
    Parent::initialize();
  }

  void advance_start() override {
    Parent::advance_start();
    ping();
  }

  void ping() {
    printf("pinger -> ponger, sending ping\n");
    send<message::Ping>(0, ponger_id);
  }

  void on_pong(message::Pong &) {
    printf("pinger, pong received\n");
    add_event(
        500, [](void *data) { static_cast<Pinger *>(data)->ping(); }, this);
  }

  rl::ActorId ponger_id;
};

struct Ponger : rl::Actor<2> {
  using Parent = Actor<2>;

  void initialize() override {
    subscribe(&Ponger::on_ping);
    Parent::initialize();
  }
  void on_ping(message::Ping &) {
    printf("ponger, ping received\n");
    send<message::Pong>(0, pinger_id);
  }
  rl::ActorId pinger_id;
};

using Supervisor =
    rl::Supervisor<rl::SupervisorBase::min_handlers_amount, Pinger, Ponger>;
using Storage = rl::traits::MessageStorage<rl::message::ChangeState,
                                           rl::message::ChangeStateAck,
                                           message::Ping, message::Pong>;
using Queue = rl::Queue<Storage, 5>; /* upto 5 messages in 1 queue */
using Planner = rl::Planner<1>;      /* upto 1 time event */

int main(int, char **) {
  /* allocate */
  Queue queue;
  Planner planner;
  rl::Context context{&queue, &planner, &get_now};
  Supervisor sup;

  /* setup */
  sup.bind(context);
  auto pinger = sup.get_child<0>();
  auto ponger = sup.get_child<1>();
  pinger->ponger_id = ponger->get_id();
  ponger->pinger_id = pinger->get_id();
  /* do not poll timer */
  sup.start(false);

  /* main cycle */
  while (true) {
    sup.process();
    auto next_event_time = planner.next_event();
    if (next_event_time) {
      auto interval = next_event_time - get_now();
      // printf("sleeping for %ld ms\n", interval);
      std::this_thread::sleep_for(TimeUnit{interval});
      // ready events will be triggered on the next
      // sup.process() call
    }
  }
  return 0;
}
